import { ICommand, ICommandHandler, ICommandOption, ICommandRegistry, ICommandsMap, IDisposable } from '../interface';
import { LinkedList, toDisposable } from '../utils';

/**
 * 命令注册中心
 *
 * @author chitanda
 * @date 2022-06-27 13:06:50
 * @export
 * @class CommandRegisterService
 * @implements {ICommandRegistry}
 */
export class CommandsRegistry implements ICommandRegistry {
  private readonly _commands = new Map<string, LinkedList<ICommand>>();

  /**
   * 注册指令
   *
   * @author chitanda
   * @date 2022-06-27 13:06:31
   * @param {(string | ICommand)} idOrCommand
   * @param {ICommandHandler} [handler]
   * @return {*}  {IDisposable}
   */
  registerCommand(idOrCommand: string | ICommand, handler?: ICommandHandler, opts?: ICommandOption): IDisposable {
    if (!idOrCommand) {
      throw new Error(`invalid command`);
    }

    if (typeof idOrCommand === 'string') {
      if (!handler) {
        throw new Error(`invalid command`);
      }
      return this.registerCommand({ id: idOrCommand, handler, opts });
    }

    const { id } = idOrCommand;

    let commands = this._commands.get(id);
    if (!commands) {
      commands = new LinkedList<ICommand>();
      this._commands.set(id, commands);
    }

    const removeFn = commands.unshift(idOrCommand);

    const ret = toDisposable(() => {
      removeFn();
      const command = this._commands.get(id);
      if (command?.isEmpty()) {
        this._commands.delete(id);
      }
    });

    return ret;
  }

  hasCommand(id: string): boolean {
    return this._commands.has(id);
  }

  getCommand(id: string): ICommand | undefined {
    const list = this._commands.get(id);
    if (!list || list.isEmpty()) {
      return undefined;
    }
    return list[Symbol.iterator]().next().value;
  }

  getCommands(): ICommandsMap {
    const result = new Map<string, ICommand>();
    for (const key of this._commands.keys()) {
      const command = this.getCommand(key);
      if (command) {
        result.set(key, command);
      }
    }
    return result;
  }

  getCommandOpt(id: string): ICommandOption | undefined {
    const cmd = this.getCommand(id);
    return cmd.opts;
  }
}
